package com.mycode.test;

import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;

@Slf4j
public class WriteServer {

    public static void main(String[] args) throws IOException {
        writeServerNonBlock();
    }

    /**
     * 通过 关注 可写事件 解决 CPU 空循环问题
     * @throws IOException
     */
    private static void writeServerNonBlock() throws IOException {
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.bind(new InetSocketAddress(8080));

        Selector selector = Selector.open();
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        while (true) {
            selector.select();
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                iterator.remove();
                if (key.isAcceptable()) {
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    socketChannel.configureBlocking(false);
                    // socketChannel 关注读事件
                    SelectionKey selectionKey = socketChannel.register(selector, SelectionKey.OP_READ);

                    // 向客户端发送数据
                    StringBuilder stringBuilder = new StringBuilder();
                    for (int i = 0; i < 30000000; i++) {
                        stringBuilder.append("a");
                    }
                    ByteBuffer byteBuffer = Charset.defaultCharset().encode(stringBuilder.toString());
                    // 不能保证一次全部写入
                    int write = socketChannel.write(byteBuffer);
                    log.debug("实际写入字节1：{}", write);
                    // 判断是否有剩余内容，没有发送给客户端
                    if (byteBuffer.hasRemaining()) {
                        // selectionKey.interestOps() 获取到原来关注的事件
                        // 在关注读事件的基础上 关注写事件
                        selectionKey.interestOps(selectionKey.interestOps() + SelectionKey.OP_WRITE);
                        // 需要把未写完的数据，挂到 selectionKey 上
                        selectionKey.attach(byteBuffer);
                    }
                }
                // 写 就绪
                else if (key.isWritable()) {
                    // 获取未写完的附件
                    ByteBuffer buffer = (ByteBuffer) key.attachment();
                    SocketChannel socketChannel = (SocketChannel) key.channel();
                    // 继续写
                    int write = socketChannel.write(buffer);
                    log.debug("实际写入字节2：" + write);
                    if (!buffer.hasRemaining()) {
                        // buffer 写完后 去掉 关注 可写事件
                        key.interestOps(key.interestOps() - SelectionKey.OP_WRITE);
                        // 附件置为 null
                        key.attach(null);
                    }
                }
            }
        }
    }

    /**
     * 当大量写入客户端数据，客户端处理不够，会存在空循环
     * @throws IOException
     */
    private static void writeServerWithBlock() throws IOException {
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.bind(new InetSocketAddress(8080));

        Selector selector = Selector.open();
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        while (true) {
            selector.select();
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                iterator.remove();
                if (key.isAcceptable()) {
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    socketChannel.configureBlocking(false);

                    // 向客户端发送数据
                    StringBuilder stringBuilder = new StringBuilder();
                    for (int i = 0; i < 30000000; i++) {
                        stringBuilder.append("a");
                    }
                    ByteBuffer byteBuffer = Charset.defaultCharset().encode(stringBuilder.toString());
                    // 不能保证一次全部写入
                    while (byteBuffer.hasRemaining()) {
                        // 不能保证一次全部写入
                        // 写比较大的数据，可能会因为 客户端的吞吐量不够，服务端一次发送，客户端需要一段时间处理，但是在客户端处理期间，服务端还是在 while 循环写入
                        int write = socketChannel.write(byteBuffer);
                        log.debug("实际写入字节：" + write);
                    }

                }
            }
        }
    }
}
